package com.yinuo.starter.anno;

import com.yinuo.starter.config.TransactionalInit;
import com.yinuo.starter.dao.TransactionalDao;
import com.yinuo.starter.factory.ConsumerFactory;
import com.yinuo.starter.properties.ConsumerProperties;
import com.yinuo.starter.properties.JmsProperties;
import org.apache.rocketmq.client.exception.MQClientException;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


@Component
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class ConsumerProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware
        , BeanClassLoaderAware, SmartInitializingSingleton {

    private ClassLoader beanClassLoader;
    private BeanFactory beanFactory;

    @Resource
    private JmsProperties jmsProperties;

    @Resource
    private ConsumerProperties consumerProperties;

    @Autowired
    private TransactionalInit transactionalInit;

    @Autowired
    private TransactionalDao transactionalDao;

    private final ConcurrentMap<Class<?>, List<ListenerMethod>> typeCache = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, ListenerMethod> methodCache = new ConcurrentHashMap<>();

    @Override
    public void afterSingletonsInstantiated() {
        if(methodCache.size() > 0){
            new ConsumerFactory(jmsProperties, consumerProperties, methodCache, transactionalInit, transactionalDao);
        }
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        List<ListenerMethod> metadata = this.typeCache.get(targetClass);
        if(metadata == null){
            metadata = buildMetadata(targetClass, bean);
            this.typeCache.putIfAbsent(targetClass, metadata);
        }
        metadata.parallelStream().forEach(m -> methodCache.putIfAbsent(
                m.annotations.topicName(), m));
        return bean;
    }

    private List<ListenerMethod> buildMetadata(Class<?> targetClass, Object bean) {
        List<ListenerMethod> methods = new ArrayList<>();
        ReflectionUtils.doWithMethods(targetClass, method -> {
            TransactionalConsumer transactionalConsumer = findListerAnnotation(method);
            if(transactionalConsumer != null){
                methods.add(new ListenerMethod(checkProxy(method, bean), transactionalConsumer, targetClass));
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
        return methods;
    }

    private TransactionalConsumer findListerAnnotation(Method method){
        Optional<TransactionalConsumer> tc = Optional.ofNullable(AnnotationUtils.findAnnotation(method,
                TransactionalConsumer.class));
        return tc.isPresent() ? tc.get() : null;
    }

    private Method checkProxy(Method method, Object bean) {
        if (AopUtils.isJdkDynamicProxy(bean)) {
            // 使用jdk代理需特殊处理
            try {
                method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
                for (Class<?> iface : proxiedInterfaces) {
                    try {
                        method = iface.getMethod(method.getName(), method.getParameterTypes());
                        break;
                    }
                    catch (NoSuchMethodException noMethod) {
                    }
                }
            }
            catch (SecurityException ex) {
                ReflectionUtils.handleReflectionException(ex);
            }
            catch (NoSuchMethodException ex) {
                throw new IllegalStateException(String.format("方法 '%s' ,类 '%s', 找不到接口的jdk代理！"
                        ,  method.getName(), method.getDeclaringClass().getSimpleName()));
            }
        }
        return method;
    }


    public static class ListenerMethod{

        public final Method method;

        public final Class<?> targetClass;

        public final TransactionalConsumer annotations;

        ListenerMethod(Method method, TransactionalConsumer annotation, Class<?> targetClass){
            this.method = method;
            this.annotations = annotation;
            this.targetClass = targetClass;
        }
    }
}
